/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting.Actions {
    public static partial class UpdateDelegates {

        private static Dictionary<Type, WeakReference> _Updaters;

        internal static T MakeUpdateDelegate<T>() where T : class {
            Type target = typeof(T);
            Type[] args = DynamicSiteHelpers.GetSiteInvokeSignature(target);

            MethodInfo method = typeof(UpdateDelegates).GetMethod("Update" + (args.Length - 1));
            if (method != null) {
                return (T)(object)Delegate.CreateDelegate(target, method.MakeGenericMethod(ArrayUtils.Insert(target, args)));
            }

            return GetOrCreateCustomUpdateDelegate<T>();
        }

        private static T GetOrCreateCustomUpdateDelegate<T>() where T : class {
            if (_Updaters == null) {
                Interlocked.CompareExchange<Dictionary<Type, WeakReference>>(ref _Updaters, new Dictionary<Type, WeakReference>(), null);
            }

            bool found;
            WeakReference wr;

            // LOCK to extract the weak reference with the updater DynamicMethod 
            lock (_Updaters) {
                found = _Updaters.TryGetValue(typeof(T), out wr);
            }

            // Extract the DynamicMethod from the WeakReference, if any
            object target = null;
            if (found && wr != null) {
                target = wr.Target;
            }

            // No target? Build new one
            if (target == null) {
                target = CreateCustomUpdateDelegate<T>();

                // Insert into dictionary
                lock (_Updaters) {
                    _Updaters[typeof(T)] = new WeakReference(target);
                }
            }

            return (T)target;
        }

        private static Delegate CreateCustomUpdateDelegate<T>() where T : class {
            Type target = typeof(T);
            Type siteOT = typeof(CallSite<T>);

            MethodInfo invoke = target.GetMethod("Invoke");
            ParameterInfo[] parameters = invoke.GetParameters();
            Type[] signature = ReflectionUtils.GetParameterTypes(parameters);

            DynamicILGen il = Snippets.Shared.CreateDynamicMethod("Update", invoke.ReturnType, signature, false);
            LocalBuilder array = il.DeclareLocal(typeof(object[]));

            il.EmitLoadArg(0);
            il.Emit(OpCodes.Castclass, siteOT);
            il.EmitInt(signature.Length - 1);
            il.Emit(OpCodes.Newarr, typeof(object));
            il.Emit(OpCodes.Stloc, array);

            for (int arg = 1; arg < signature.Length; arg++) {
                il.Emit(OpCodes.Ldloc_S, array);
                il.EmitInt(arg - 1);            // index into array
                il.EmitLoadArg(arg);            // argument
                il.EmitBoxing(signature[arg]);
                il.Emit(OpCodes.Stelem_Ref);
            }

            // CallSite<T>.UpdateAndExecute(array)
            il.Emit(OpCodes.Ldloc_S, array);
            if (invoke.ReturnType == typeof(object)) {
                il.Emit(OpCodes.Tailcall);
            }
            il.Emit(OpCodes.Call, siteOT.GetMethod("UpdateAndExecute", BindingFlags.Instance | BindingFlags.Public));
            if (invoke.ReturnType != typeof(object)) {
                il.Emit(OpCodes.Unbox_Any, invoke.ReturnType);
            }
            il.Emit(OpCodes.Ret);

            MethodInfo mi = il.Finish();
            return ReflectionUtils.CreateDelegate(mi, typeof(T));
        }
    }
}
